#pragma once

#include "Globals.h"
#include "Exception.h"

#include <vector>

typedef Exception UriException;

/// RFC 3986
class URI
{
public:
	URI();
	explicit URI(const std::string& uri);
	explicit URI(const char* uri);
	URI(const std::string& scheme, const std::string& pathEtc);
	URI(const std::string& scheme, const std::string& authority, const std::string& pathEtc);
	URI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query);
	URI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query, const std::string& fragment);
	URI(const URI& uri);
	URI(const URI& baseURI, const std::string& relativeURI);
	~URI();
	
	URI& operator = (const URI& uri);
	URI& operator = (const std::string& uri);
	URI& operator = (const char* uri);

	void Swap(URI& uri);
		
	void Clear();
	std::string ToString() const;

	// see: http://www.iana.org/assignments/uri-schemes
	const std::string& GetScheme() const;
	void SetScheme(const std::string& scheme);

	const std::string& GetUserInfo() const;
	void SetUserInfo(const std::string& userInfo);

	const std::string& GetHost() const;
	void SetHost(const std::string& host);
		
	unsigned short GetPort() const;
	void SetPort(unsigned short port);
		
	std::string GetAuthority() const;
	void SetAuthority(const std::string& authority);
		
	const std::string& GetPath() const;
	void SetPath(const std::string& path);
	
	std::string GetQuery() const;
	void SetQuery(const std::string& query);	

	const std::string& GetRawQuery() const;
	void SetRawQuery(const std::string& query);	
	
	const std::string& GetFragment() const;
	void SetFragment(const std::string& fragment);
		
	void SetPathEtc(const std::string& pathEtc);
	std::string GetPathEtc() const;
		
	std::string GetPathAndQuery() const;

	void Resolve(const std::string& relativeURI);
	void Resolve(const URI& relativeURI);

	bool IsRelative() const;
	
	bool Empty() const;
		
	bool operator == (const URI& uri) const;
	bool operator == (const std::string& uri) const;
	bool operator != (const URI& uri) const;
	bool operator != (const std::string& uri) const;

	void Normalize();
	
	void GetPathSegments(std::vector<std::string>& segments);

	static void Encode(const std::string& str, const std::string& reserved, std::string& encodedStr);
	static void Decode(const std::string& str, std::string& decodedStr);

protected:
	bool Equals(const URI& uri) const;
	bool IsWellKnownPort() const;
	unsigned short GetWellKnownPort() const;
	void Parse(const std::string& uri);
	void ParseAuthority(std::string::const_iterator& it, const std::string::const_iterator& end);
	void ParseHostAndPort(std::string::const_iterator& it, const std::string::const_iterator& end);
	void ParsePath(std::string::const_iterator& it, const std::string::const_iterator& end);
	void ParsePathEtc(std::string::const_iterator& it, const std::string::const_iterator& end);
	void ParseQuery(std::string::const_iterator& it, const std::string::const_iterator& end);
	void ParseFragment(std::string::const_iterator& it, const std::string::const_iterator& end);
	void MergePath(const std::string& path);
	void RemoveDotSegments(bool removeLeading = true);
	static void GetPathSegments(const std::string& path, std::vector<std::string>& segments);
	void BuildPath(const std::vector<std::string>& segments, bool leadingSlash, bool trailingSlash);

	static const std::string RESERVED_PATH;
	static const std::string RESERVED_QUERY;
	static const std::string RESERVED_FRAGMENT;
	static const std::string ILLEGAL;
	
private:
	std::string    m_scheme;
	std::string    m_userInfo;
	std::string    m_host;
	unsigned short m_port;
	std::string    m_path;
	std::string    m_query;
	std::string    m_fragment;
};

// inlines

inline const std::string& URI::GetScheme() const
{
	return m_scheme;
}
	

inline const std::string& URI::GetUserInfo() const
{
	return m_userInfo;
}

	
inline const std::string& URI::GetHost() const
{
	return m_host;
}
	
inline const std::string& URI::GetPath() const
{
	return m_path;
}
	
inline const std::string& URI::GetRawQuery() const
{
	return m_query;
}
	
inline const std::string& URI::GetFragment() const
{
	return m_fragment;
}

inline void swap(URI& u1, URI& u2)
{
	u1.Swap(u2);
}

